// PBC Client is a simple http/https proxy, easy to config and start
package main

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"net/url"
	"os"
	"strings"
)

const pbcVERSION = "1.0.0"

func main() {
	// get args and port and any other things
	var port string
	processPBCArgs(os.Args, &port)
	fmt.Printf("PBC client listening on %s...\n", port)

	// start a port listen
	lisAddr := ":" + port
	listener, err := net.Listen("tcp", lisAddr)
	if err != nil {
		fmt.Printf("PBC client failed to start, err: %s\n", err.Error())
		return
	}

	// get all connect
	for {
		client, err := listener.Accept()
		if err != nil {
			fmt.Printf("Connection error, err: %s\n", err.Error())
		}
		go handleProxyRequest(client)
	}
}

func handleProxyRequest(client net.Conn) {
	// read data from client
	var data [2048]byte
	reqLen, _ := client.Read(data[:])

	// get target host info
	var method, host, address string
	if _, err := fmt.Sscanf(string(data[:bytes.IndexByte(data[:reqLen], '\n')]), "%s%s", &method, &host); err != nil {
		fmt.Printf("Scanf for method and host error, err: %s\n", err.Error())
		return
	}
	fmt.Printf("Proceed %s on Host %s\n", method, host)

	// parse host to an url object
	requestURL, err := url.Parse(host)
	if err != nil {
		fmt.Printf("Parse host error, err: %s\n", err.Error())
		return
	}

	// get tcp connect address
	if requestURL.Opaque == "443" {
		address = requestURL.Scheme + ":443"
	} else {
		if strings.Index(requestURL.Host, ":") == -1 {
			address = requestURL.Host + ":80"
		} else {
			address = requestURL.Host
		}
	}

	// connect to address
	server, err := net.Dial("tcp", address)
	if err != nil {
		fmt.Printf("Dial to %s error, err: %s\n", address, err.Error())
		return
	}
	// https just connect and transfer data (if there is a CONNECT method, just transfer)
	// http need to tell server http data to get response
	// see: https://www.oschina.net/question/12_117516
	if method == "CONNECT" {
		if _, err := client.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")); err != nil {
			fmt.Printf("Connection establish error, err: %s\n", err.Error())
			return
		}
	} else {
		if _, err := server.Write(data[:]); err != nil {
			fmt.Printf("Write data to proxy connection error, err: %s\n", err.Error())
			return
		}
	}

	defer func() {
		_ = client.Close()
		_ = server.Close()
	}()

	// everything is ready, transfer data
	// write data from client to server parallel
	go func() {
		_, _ = io.Copy(server, client)
	}()

	// write data from server to client
	_, _ = io.Copy(client, server)
}